package bancosys.tec.component;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;

/**
 * Localizador de controllers. Utilização: Após registrar um controller pacote.exemplo.ExemploController, o locator irá devolver essa
 * instância para todo objeto pacote.exemplo.* .
 * 
 * @created Feb 7, 2007
 * @author Gustavo Almeida
 */
public class ControllerLocator implements BeanFactoryAware {

    private BeanFactory beanFactory;

    private Map<Class<?>, Controller> cache = new ConcurrentHashMap<Class<?>, Controller>();

    private Map<String, Controller> controllerMap = new TreeMap<String, Controller>(new PackageComparator());

    private String defaultControllerId = null;

    /**
     * Dada uma classe localiza seu controller.
     * 
     * @param clazz classe.
     * @return controller.
     */
    public Controller findControllerForClass(Class<?> clazz) {
        Controller cachedController = this.cache.get(clazz);
        if (cachedController != null) {
            return cachedController;
        }
        for (Map.Entry<String, Controller> entry : this.controllerMap.entrySet()) {
            if (clazz.getName().startsWith(entry.getKey())) {
                this.cache.put(clazz, entry.getValue());
                return entry.getValue();
            }
        }
        if (this.beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory lbf = (ListableBeanFactory) this.beanFactory;
            Map controllers = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, Controller.class);
            Iterator<?> controllerIterator = controllers.values().iterator();
            while (controllerIterator.hasNext()) {
                Controller controller = (Controller) controllerIterator.next();
                this.registerController(controller);
                for (String managedPackage : controller.getManagedPackages()) {
                    if (clazz.getName().startsWith(managedPackage)) {
                        this.cache.put(clazz, controller);
                        return controller;
                    }
                }
            }
        }
        if (this.defaultControllerId != null) {
            return (Controller) this.beanFactory.getBean(this.defaultControllerId);
        }
        return null;
    }

    /**
     * Registra um controller.
     * 
     * @param controller controller.
     */
    public void registerController(Controller controller) {
        for (String packageName : controller.getManagedPackages()) {
            this.controllerMap.put(packageName, controller);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * Comparator que ordena strings por ordem decrescente de tamanho e desempata pelo compare padrão do string.
     * 
     * @created Feb 12, 2007
     * @author Gustavo Almeida
     */
    private static class PackageComparator implements Comparator<String>, Serializable {
        private static final long serialVersionUID = -8278969623065260231L;

        /**
         * Compara duas strings
         * 
         * @param o1 string 1
         * @param o2 string 2
         * @see Comparator#compare(Object, Object)
         * @see PackageComparator
         * @return -1, 0 ou 1
         */
        public int compare(String o1, String o2) {
            if (o1 == o2) {
                return 0;
            }
            if (o1 == null) {
                return -1;
            }
            if (o2 == null) {
                return 1;
            }
            if (o1.equals(o2)) {
                return 0;
            }
            if (o1.length() > o2.length()) {
                return -1;
            }
            if (o1.length() < o2.length()) {
                return 1;
            }
            return o1.compareTo(o2);
        }
    }

    /**
     * @param defaultControllerId defaultControllerId the defaultControllerId to set
     */
    public void setDefaultControllerId(String defaultControllerId) {
        this.defaultControllerId = defaultControllerId;
    }
}
